package primitives;


import javax.vecmath.*;

import shaders.MatteShader;
import shaders.Shader;
import essential.Axis;
import essential.Ray;
import essential.RayCastResult;

public class Sphere extends Primitive {
	private Vector3d center;
	private double sqRadius, radius, rRadius;
	protected Matrix4d transform = new Matrix4d();
	private Matrix4d tmpMat = new Matrix4d();
	private Vector4d tmpVec = new Vector4d(0, 0, 0, 0);

	public Sphere(){
		this(new Vector3d(),1);
		this.center = new Vector3d();
		
	}
	
	public Renderable copy() {
		Sphere s = new Sphere(center, radius);
		transform = new Matrix4d(s.transform);
		s.setShader(getShader().copy());
		return s;
	}

	public Sphere(Vector3d center, double radius) {
		this.center = center;
		this.sqRadius = radius * radius;
		this.radius = radius;
		this.rRadius = 1.0f / radius;
		this.setShader(new MatteShader());
		transform.setIdentity();
		recalcAABB();
	}

	public void recalcAABB() {
		Vector3d a = new Vector3d(center);
		Vector3d b = new Vector3d(center);
		a.add(new Vector3d(-radius, -radius, -radius));
		b.add(new Vector3d(radius, radius, radius));
		aabb = new AxisAlignedBoundingBox(a, b);
	}
	
	public Vector3d getNormal(Vector3d v) {
		Vector3d vec = new Vector3d(v);
		vec.sub(center);
		vec.normalize();

		return vec;
	}

	public RayCastResult intersect(Ray ray) {
		if (aabb.intersection(ray) == RayCastResult.MISS)
			return RayCastResult.MISS;

		Vector3d vecAux = new Vector3d(ray.getOrigin());
		vecAux.sub(center);

		double b = -vecAux.dot(ray.getDirection());
		double det = (b * b) - vecAux.dot(vecAux) + sqRadius;
		RayCastResult retval = RayCastResult.MISS;

		if (det > 0) {
			det = Math.sqrt(det);
			double i1 = b - det;
			double i2 = b + det;

			if (i2 > 0) {
				if (i1 < 0) {
					if (i2 < ray.getImpactDist()) {
						// ray.setImpactDist(i2);
						// retval = RayCastResult.HIT;
						retval = RayCastResult.MISS;
					}
				} else {
					if (i1 < ray.getImpactDist()) {
						ray.setLastRenderable(this);
						ray.setImpactDist(i1);
						ray.impactNormal = this.getNormal(ray.getIntersectionPoint());
						
						if(getShader().hasTexture()){
						//Sphere UV Mapping
							Vector3d sp = ray.getIntersectionPoint();
							sp.sub(this.center);
							ray.impactColor = getShader().getColor(UVMap(sp),Shader.MappingType.Sphere);
						}else{
							ray.impactColor = getShader().getColor(ray);
						}
						
						retval = RayCastResult.HIT;
					}
				}
			}
		}
		return retval;
	}
	
	public Vector2d UVMap(Vector3d sp) {
		Vector2d uv = new Vector2d();
	
		uv.x = sp.x/(Math.sqrt(sp.x*sp.x+sp.y*sp.y+sp.z*sp.z));
		uv.y = sp.y/(Math.sqrt(sp.x*sp.x+sp.y*sp.y+sp.z*sp.z));

		
		return uv;
	}

	public void setCenter(Vector3d center) {
		this.center = center;
	}

	public Vector3d getCenter() {
		return center;
	}

	public void setRadius(double radius) {
		this.sqRadius = radius * radius;
		this.radius = radius;
		this.rRadius = 1.0f / radius;
	}

	public double getRadius() {
		return radius;
	}

	public double getSqRadius() {
		return sqRadius;
	}

	public double getReverseRadius() {
		return rRadius;
	}

	public double distanceTo(Vector3d v) {
		Vector3d dist = new Vector3d(v);
		dist.sub(center);

		return Math.abs(dist.length() - radius);
	}
	
	@Override
	public String toString() {
		return "Sphere(" + center + ", " + radius + ")";
	}

	@Override
	public Vector3d getOrigin() {
		return center;
	}

	@Override
	public void rotate(Axis axis, double angles) {
		tmpMat.setIdentity();

		switch(axis) {
		case X:
			tmpMat.rotX(angles);
		case Y:
			tmpMat.rotY(angles);
		case Z:
			tmpMat.rotZ(angles);
		}
		transform.mul(tmpMat, transform);

		//TODO implement UV Rotation
	}

	@Override
	public void scale(Vector3d v) {
		double scaleFactor = Math.max(v.x, Math.max(v.y, v.z));
		setRadius(radius * scaleFactor);

		tmpMat.setIdentity();
		tmpMat.m00 = v.x;
		tmpMat.m11 = v.y;
		tmpMat.m22 = v.z;
		transform.mul(tmpMat, transform);
	}

	@Override
	public void translate(Vector3d pos) {
		tmpMat.setIdentity();
		tmpMat.setTranslation(pos);
		transform.mul(tmpMat, transform);
	}

	@Override
	public void apply(Transform t) {
		transform.mul(t.matrix, transform);
	}

	@Override
	public void set(Transform t) {
		transform.setIdentity();
		transform.mul(t.matrix, transform);
	}

	Vector3d tmpVecR = new Vector3d();
	
	@Override
	public void freezeTransform() {
		tmpVec.set(center);
		tmpVec.w = 1.0;
		transform.transform(tmpVec);
		center.set(tmpVec.x, tmpVec.y, tmpVec.z);

		tmpVec.set(0, 1, 0, 0);
		tmpVec.scale(radius);
		transform.transform(tmpVec);
		tmpVecR.set(tmpVec.x, tmpVec.y, tmpVec.z);
		tmpVecR.sub(center);
		radius = tmpVec.length();

		recalcAABB();
	}

	
}
